LIST_OR
) ];
}
- $this->addJoinConds( [ 'ug1' => [ 'LEFT OUTER JOIN',
+ $this->addJoinConds( [ 'ug1' => [ 'LEFT JOIN',
array_merge( [
'ug1.ug_user=user_id',
'ug1.ug_expiry IS NULL OR ug1.ug_expiry >= ' . $db->addQuotes( $db->timestamp() )
$limitGroups = array_unique( $limitGroups );
$this->addTables( 'user_groups' );
$this->addJoinConds( [ 'user_groups' => [
- $excludeGroups ? 'LEFT OUTER JOIN' : 'JOIN',
+ $excludeGroups ? 'LEFT JOIN' : 'JOIN',
[
'ug_user=' . $revQuery['fields']['rev_user'],
'ug_group' => $limitGroups,
* Uses asynchronous I/O, allowing purges to be done in a highly parallel
* manner.
*
- * Could be replaced by curl_multi_exec() or some such.
+ * @todo Consider using MultiHttpClient.
*/
class SquidPurgeClient {
/** @var string */
}
/**
- * Checks that the given URI is a valid one. Hardcoding the
- * protocols, because we only want protocols that both cURL
- * and php support.
+ * Check that the given URI is a valid one.
*
- * file:// should not be allowed here for security purpose (r67684)
+ * This hardcodes a small set of protocols only, because we want to
+ * deterministically reject protocols not supported by all HTTP-transport
+ * methods.
+ *
+ * "file://" specifically must not be allowed, for security purpose
+ * (see <https://www.mediawiki.org/wiki/Special:Code/MediaWiki/r67684>).
*
* @todo FIXME this is wildly inaccurate and fails to actually check most stuff
*
* @param string $container Resolved container name
* @param string $dir Resolved path relative to container
* @param array $params
- * @return Traversable|array|null Returns null on failure
+ * @return Traversable|string[]|null Returns null on failure
*/
abstract public function getFileListInternal( $container, $dir, array $params );
$scalerThumbUrl = $scalerBaseUrl . '/' . $file->getUrlRel() .
'/' . rawurlencode( $scalerThumbName );
- // make a curl call to the scaler to create a thumbnail
+ // make an http request based on wgUploadStashScalerBaseUrl to lazy-create
+ // a thumbnail
$httpOptions = [
'method' => 'GET',
'timeout' => 5 // T90599 attempt to time out cleanly
* scripts, so the blacklist needs to check them all.
*
* @param string $filename
- * @return array
+ * @return array [ string, string[] ]
*/
public static function splitExtensions( $filename ) {
$bits = explode( '.', $filename );
* Perform case-insensitive match against a list of file extensions.
* Returns an array of matching extensions.
*
- * @param array $ext
- * @param array $list
+ * @param string[] $ext
+ * @param string[] $list
* @return bool
*/
public static function checkFileExtensionList( $ext, $list ) {
* non-existent/anonymous user accounts.
*/
public function getFirstEditTimestamp() {
+ return $this->getEditTimestamp( true );
+ }
+
+ /**
+ * Get the timestamp of the latest edit
+ *
+ * @since 1.33
+ * @return string|bool Timestamp of first edit, or false for
+ * non-existent/anonymous user accounts.
+ */
+ public function getLatestEditTimestamp() {
+ return $this->getEditTimestamp( false );
+ }
+
+ /**
+ * Get the timestamp of the first or latest edit
+ *
+ * @param bool $first True for the first edit, false for the latest one
+ * @return string|bool Timestamp of first or latest edit, or false for
+ * non-existent/anonymous user accounts.
+ */
+ private function getEditTimestamp( $first ) {
if ( $this->getId() == 0 ) {
return false; // anons
}
$actorWhere = ActorMigration::newMigration()->getWhere( $dbr, 'rev_user', $this );
$tsField = isset( $actorWhere['tables']['temp_rev_user'] )
? 'revactor_timestamp' : 'rev_timestamp';
+ $sortOrder = $first ? 'ASC' : 'DESC';
$time = $dbr->selectField(
[ 'revision' ] + $actorWhere['tables'],
$tsField,
[ $actorWhere['conds'] ],
__METHOD__,
- [ 'ORDER BY' => "$tsField ASC" ],
+ [ 'ORDER BY' => "$tsField $sortOrder" ],
$actorWhere['joins']
);
if ( !$time ) {
/**
* Get iterator for links for categories.
* @param IDatabase $dbr
- * @param array $ids List of page IDs
+ * @param int[] $ids List of page IDs
* @return Traversable
*/
protected function getCategoryLinksIterator( IDatabase $dbr, array $ids ) {
* @ingroup Maintenance
*/
class CleanupInvalidDbKeys extends Maintenance {
- /** @var array List of tables to clean up, and the field prefix for that table */
+ /** @var array[] List of tables to clean up, and the field prefix for that table */
protected static $tables = [
// Data tables
[ 'page', 'page' ],
}
/**
- * @param array $matches
+ * @param string[] $matches
* @return string
*/
protected function hexChar( $matches ) {
* @param IDatabase $dbw
* @param string[] $indexFields Fields in the index being ordered by
* @param object $row Database row
- * @return array [ string $next, string $display ]
+ * @return string[] [ string $next, string $display ]
*/
private function makeNextCond( $dbw, $indexFields, $row ) {
$next = '';
/**
* Things that were formerly globals and should still be converted
*
- * @var array
+ * @var string[]
*/
protected $formerGlobals = [
'TrackingCategories',
/**
* No longer supported globals (with reason) should not be converted and emit a warning
*
- * @var array
+ * @var string[]
*/
protected $noLongerSupportedGlobals = [
'SpecialPageGroups' => 'deprecated', // Deprecated 1.21, removed in 1.26
/**
* Keys that should be put at the top of the generated JSON file (T86608)
*
- * @var array
+ * @var string[]
*/
protected $promote = [
'name',
* @param FileBackend $src
* @param FileBackend $dst
* @param string $backendRel
- * @return array (rel paths in $src minus those in $dst)
+ * @return string[] (rel paths in $src minus those in $dst)
*/
protected function getListingDiffRel( FileBackend $src, FileBackend $dst, $backendRel ) {
$srcPathsRel = $src->getFileList( [
}
/**
- * @param array $srcPathsRel
+ * @param string[] $srcPathsRel
* @param string $backendRel
* @param FileBackend $src
* @param FileBackend $dst
}
/**
- * @param array $dstPathsRel
+ * @param string[] $dstPathsRel
* @param string $backendRel
* @param FileBackend $dst
* @return void
* Delete one or more revisions from the database
* Do this inside a transaction
*
- * @param array $id Array of revision id values
+ * @param int[] $id Array of revision id values
* @param IDatabase $dbw Master DB handle
*/
- private function deleteRevs( $id, &$dbw ) {
- if ( !is_array( $id ) ) {
- $id = [ $id ];
- }
+ private function deleteRevs( array $id, &$dbw ) {
$dbw->delete( 'revision', [ 'rev_id' => $id ], __METHOD__ );
// Delete from ip_changes should a record exist.
/**
* Get iterator for links for categories.
* @param IDatabase $dbr
- * @param array $ids List of page IDs
+ * @param int[] $ids List of page IDs
* @return Traversable
*/
public function getCategoryLinksIterator( IDatabase $dbr, array $ids ) {
$sql = "SELECT DISTINCT il_to, img_name
FROM $imagelinks
- LEFT OUTER JOIN $image
+ LEFT JOIN $image
ON il_to=img_name";
$result = $dbr->query( $sql );
/**
* Get an array of author names from a documentation comment containing @author declarations.
* @param string $comment Documentation comment
- * @return array Array of author names (strings)
+ * @return string[] Array of author names
*/
protected function getAuthorsFromComment( $comment ) {
$matches = null;
. "(this may take a while on a large wiki)\n" );
$result = $dbw->query( "
SELECT *
- FROM $page LEFT OUTER JOIN $revision ON page_latest=rev_id
+ FROM $page LEFT JOIN $revision ON page_latest=rev_id
" );
$found = 0;
foreach ( $result as $row ) {
// clean up
$block->delete();
}
+
+ /**
+ * @covers User::getFirstEditTimestamp
+ * @covers User::getLatestEditTimestamp
+ */
+ public function testGetFirstLatestEditTimestamp() {
+ $clock = MWTimestamp::convert( TS_UNIX, '20100101000000' );
+ MWTimestamp::setFakeTime( function () use ( &$clock ) {
+ return $clock += 1000;
+ } );
+ $user = $this->getTestUser()->getUser();
+ $firstRevision = self::makeEdit( $user, 'Help:UserTest_GetEditTimestamp', 'one', 'test' );
+ $secondRevision = self::makeEdit( $user, 'Help:UserTest_GetEditTimestamp', 'two', 'test' );
+ // Sanity check: revisions timestamp are different
+ $this->assertNotEquals( $firstRevision->getTimestamp(), $secondRevision->getTimestamp() );
+
+ $this->assertEquals( $firstRevision->getTimestamp(), $user->getFirstEditTimestamp() );
+ $this->assertEquals( $secondRevision->getTimestamp(), $user->getLatestEditTimestamp() );
+ }
+
+ /**
+ * @param User $user
+ * @param string $title
+ * @param string $content
+ * @param string $comment
+ * @return \MediaWiki\Revision\RevisionRecord|null
+ */
+ private static function makeEdit( User $user, $title, $content, $comment ) {
+ $page = WikiPage::factory( Title::newFromText( $title ) );
+ $content = ContentHandler::makeContent( $content, $page->getTitle() );
+ $updater = $page->newPageUpdater( $user );
+ $updater->setContent( 'main', $content );
+ return $updater->saveRevision( CommentStoreComment::newUnsavedComment( $comment ) );
+ }
}